﻿using System.CodeDom;
using System.Linq;
using EnvDTE;
using Roslyn.Compilers.CSharp;

namespace InterfaceWeaver.CustomTool
{
    public static class Weaver
    {
        /// <summary>
        /// Weaves the specified code.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <returns></returns>
        public static CodeCompileUnit Weave(string code)
        {
            return Weave(code, null);
        }

        /// <summary>
        /// Weaves the specified code.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <param name="project">The project.</param>
        /// <returns></returns>
        public static CodeCompileUnit Weave(string code, Project projectContext)
        {
            // Initialization
            var weavedCode = new CodeCompileUnit();
            var codeToWeave = SyntaxTree.ParseText(code);
            var rootToWeave = codeToWeave.GetRoot();
            var aspectDispatcher = new AspectDispatcher(rootToWeave, weavedCode);

            // Recover default aspects
            var defaultCodeOwner = string.Join(" ",
                AspectConfiguration.GetDefaultAttributeTypes()
                .Select(type => string.Format("[{0}]", type.Name)).ToArray());
            var defaultAttributes = SyntaxTree.ParseText(defaultCodeOwner)
                .GetRoot().DescendantNodes().OfType<AttributeSyntax>().ToList();

            // Dive into the syntax tree and build the codeCompileUnit
            // namespaces implementation
            var namespacesToImplement = rootToWeave.DescendantNodes().OfType<NamespaceDeclarationSyntax>().ToList();
            foreach (var namespaceToImplement in namespacesToImplement)
            {
                aspectDispatcher.ManageNamespaceSyntax(namespaceToImplement, defaultAttributes);

                // interfaces implementation
                var interfacesToImplement = namespaceToImplement.ChildNodes().OfType<InterfaceDeclarationSyntax>().ToList();
                foreach (var interfaceToImplement in interfacesToImplement)
                {
                    aspectDispatcher.ManageInterfaceSyntax(interfaceToImplement);

                    // Property implementation
                    var propertyToImplement = interfaceToImplement.ChildNodes().OfType<PropertyDeclarationSyntax>().ToList();
                    var propertyStreamTuples = propertyToImplement.Select(property =>
                        {
                            AttributeSyntax[] unknownAttributes = null;
                            var aspectTuple = aspectDispatcher.GetPropertyAspectTupleStream(property, out unknownAttributes);
                            return new
                                {
                                    Property = property,
                                    AspectTuple = aspectTuple,
                                    UnknownAttributes = unknownAttributes
                                };
                        }).ToList();
                    
                    foreach (var memberToImplement in propertyToImplement)
                    {
                        aspectDispatcher.HandlePropertySyntax(
                            memberToImplement, 
                            propertyStreamTuples.ToDictionary(item => item.Property, item => item.AspectTuple),
                            propertyStreamTuples.First(item => item.Property == memberToImplement).UnknownAttributes);
                    }

                    // Event implementation
                    var eventsToImplement = interfaceToImplement.ChildNodes().OfType<EventDeclarationSyntax>().ToList();
                    foreach (var eventToImplement in eventsToImplement)
                    {
                        aspectDispatcher.HandleEventSyntax(
                            eventToImplement,
                            null, //eventStreamTuples.ToDictionary(item => item.Event, item => item.AspectTuple),
                            null);//eventStreamTuples.First(item => item.Event == eventToImplement).UnknownAttributes);
                    }
                }   
            }

            return weavedCode;
        }
    }
}
